﻿using System;
using System.Activities;
using System.Activities.Presentation;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

namespace Vision.Core.ActivityComponents.Common
{
    /// <summary>
    /// 
    /// </summary>
    [Designer(typeof(Vision.Core.ActivityComponents.Common.RangeEnumerationDesigner))]
    [ContentProperty("Body")]
    public class RangeEnumeration : NativeActivity
    {
        Variable<int> loopVariable;

        [Browsable(false)]
        [DefaultValue(null)]
        public ActivityAction<int> Body { get; set; }

        [Category("Input")]
        [RequiredArgument]
        [DefaultValue(0)]
        public InArgument<int> Start { get; set; }

        [Category("Input")]
        [RequiredArgument]
        [DefaultValue(0)]
        public InArgument<int> Stop { get; set; }

        [Category("Input")]
        [RequiredArgument]
        [DefaultValue(0)]
        public InArgument<int> Step { get; set; }
        
        /// <summary>
        /// 
        /// </summary>
        public RangeEnumeration(): base()
        {
            this.loopVariable = new Variable<int>("LoopVariable");

            this.Body = new ActivityAction<int>(){
                Argument = new DelegateInArgument<int>() { Name = "index" }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="metadata"></param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            // add the arguments to the argument collection
            metadata.AddArgument(new RuntimeArgument("Start", typeof(int), ArgumentDirection.In, true));
            metadata.AddArgument(new RuntimeArgument("Stop", typeof(int), ArgumentDirection.In, true));
            metadata.AddArgument(new RuntimeArgument("Step", typeof(int), ArgumentDirection.In, true));

            // add the body to the delegates collection
            metadata.AddDelegate(this.Body);

            // Register variables used in the custom activity.
            metadata.AddImplementationVariable(this.loopVariable);
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected override void Execute(NativeActivityContext context)
        {
            int start = this.Start.Get(context);
            int stop = this.Stop.Get(context);
            int step = this.Step.Get(context);

            // Raise exception when step is 0 as this causes infinite loop.
            if (step == 0){
                throw new InvalidOperationException("Setting step = 0 causes an infinite loop.");
            }
            // If you're running this sample in debug mode in Visual Studio, it may breakpoint
            // near here because the sample intentionally passes in invalid input to demonstrate
            // this check.

            // Only run when there is an action activity set in the body, so that at each step 
            // an action is taken. There will be no execution with invalid range and step values.
            if (this.Body != null && this.Body.Handler != null && Condition(start, stop, step)){
                this.loopVariable.Set(context, start);
                context.ScheduleAction(this.Body, this.loopVariable.Get(context), this.AddStep);                                
            }
        }

        /// <summary>
        /// Add step value to loop variable after loop body is executed.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="completedInstance"></param>
        private void AddStep(NativeActivityContext context, ActivityInstance completedInstance)
        {
            int value = this.loopVariable.Get(context);
            int step = this.Step.Get(context);
            value += step;
            this.loopVariable.Set(context, value);
            int stop = this.Stop.Get(context);

            if (Condition(value, stop, step)){
                context.ScheduleAction(this.Body, this.loopVariable.Get(context), this.AddStep);                                
            }
        }

        /// <summary>
        /// Check that enumeration parameters make sense.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="stop"></param>
        /// <param name="step"></param>
        /// <returns></returns>
        private bool Condition(int start, int stop, int step)
        {
            return ((step > 0 && start <= stop) || (step < 0 && start >= stop));
        }
    }
}
